package org.filecollector.filecontroller.virtualcatalogue.virtualfile;

import lombok.NonNull;

import java.io.File;
import java.io.IOException;
/**
 * Abstract factory that produces objects of {@link VirtualFileClass} which implements {@link VirtualFile} interface.
 * <p>
 *  This class is <b>abstract</b>, all methods are <b>static</b>.
 * </p>
 * <p>
 *  Methods of this class provides basics checking of each input object but not contain any business logic except calling
 *  factory methods of {@link VirtualFileDao}, {@link VirtualFileDependencies} and {@link VirtualFileClass}.
 * </p>
 * <p>
 *  Methods of this class should only throw {@link InvalidFileException} exceptions during normal operation
 * </p>
 * @version 1.0
 */
public abstract class VirtualFilesFactory {
    /**
     * This method implements creation of new file during the creation of VirtualFile object.
     * @apiNote if <b>file already exists</b> method throws {@link InvalidFileException} with {@link FileTroubles} set as
     * <b>Incorrect Id</b> because during the normal operation this <b>shall not happen</b>.
     * @param directory is directory where file should be created. Directory security setting should allow to write and
     *                  read
     * @param id is ID of file and its name in <b>directory</b>
     * @param dependencies is the {@link  VirtualFileDependencies} that will be used in new object
     * @return Object implements {@link VirtualFile} interface
     * @throws InvalidFileException for many reasons when it is not possible to create file
     */
    public static VirtualFile createNewFile(
            @NonNull File directory,
            @NonNull Long id,
            @NonNull VirtualFileDependencies dependencies) {
        //Checking directory status
        if (!directory.isDirectory()) {
            throw  new InvalidFileException(
                    String.format(
                            "%s is not a directory",
                            directory.getPath()
                    ),
                    FileTroubles.NOT_A_FILE,
                    directory.toPath()
            );
        }
        //Checking directory abilities
        if (!(directory.canRead() && directory.canWrite())) {
            throw new InvalidFileException(
                    String.format(
                            "Directory %s have abilities: to write[%s]; to read[%s]",
                            directory.getPath(),
                            directory.canWrite()? "allow":"FORBID",
                            directory.canRead()? "allow":"FORBID"
                    ),
                    FileTroubles.SECURITY_TROUBLES
            );
        }
        //Creation of new file and throwing an Exception if it already exists
        var file = new File(directory, Long.toHexString(id));
        if (file.exists()) {
            throw new InvalidFileException(
                    String.format(
                            "File %s wasn't created: File with id %s already exists",
                            file.getPath(),
                            Long.toHexString(id)
                    ),
                    FileTroubles.INCORRECT_ID,
                    file.toPath()
            );
        }
        //Creation of file. If File::createNewFile() returns false(Look javadoc of File) exception throws
        try {
            if (!file.createNewFile()) {
                throw new InvalidFileException(
                        String.format(
                                "Unknown troubles for file %s",
                                file.getPath()
                        ),
                        FileTroubles.UNKNOWN_TROUBLES,
                        file.toPath()
                );
            }
            //Catching declared File::createNewFile() exceptions and rethrowing them as InvalidFileException
        } catch (IOException exception) {
            throw new InvalidFileException(
                    String.format(
                            "File %s doesn't created: %s",
                            file.getPath(),
                            exception.getMessage()
                    ),
                    FileTroubles.FILE_NOT_EXISTS,
                    file.toPath()
            );
        } catch (SecurityException exception) {
            throw new InvalidFileException(
                    String.format(
                            "Security exception for file %s",
                            file.getPath()
                    ),
                    FileTroubles.SECURITY_TROUBLES,
                    file.toPath()
            );
        }
        //Creating VirtualFile object
        var data = VirtualFileDao.construct(id, file);
        return new VirtualFileClass(data, dependencies);
    }

    /**
     * This method implements opening of existing file and creating VirtualFile objects
     * @param file is File that will be encapsulated into {@link VirtualFile}
     * @param dependencies is {@link VirtualFileDependencies} that will be used in new {@link VirtualFile} object.
     * @return {@link VirtualFile} object
     * @throws InvalidFileException for many reasons that would not allow to open file
     * @apiNote File name should be a hex number that uses capital letters like: {@code 0123456789ABCDEF} with
     * no extensions
     */
    public static VirtualFile openExistingFile(File file, VirtualFileDependencies dependencies) {
        //Checking matching file name to requirements
        if (!VirtualFileDao.getFileNamePattern().matcher(file.getName()).matches()) {
            throw new InvalidFileException(
                    String.format("%s has invalid name", file.getPath()),
                    FileTroubles.INCORRECT_NAME,
                    file.toPath()
            );
        }
        //Checking for write(read)ability and attempt to turn on them
        try {
            if (!file.canRead()) {
                if (!file.setReadable(true)) {
                    throw new InvalidFileException(
                            String.format("File %s cannot be set readable", file.getPath()),
                            FileTroubles.UNREADABLE,
                            file.toPath()
                    );
                }
            }
        } catch (SecurityException e) {
            throw new InvalidFileException(
                    String.format("File %s cannot be read", file.getPath()),
                    FileTroubles.UNREADABLE,
                    file.toPath()
            );
        }
        //Checking that file is not directory
        if (file.isDirectory()) {
            throw new InvalidFileException(
                    String.format("%s is a directory", file.getPath()),
                    FileTroubles.NOT_A_FILE,
                    file.toPath()
            );
        }
        //Checking that file is file(not link or stuff)
        if (!file.isFile()) {
            throw new InvalidFileException(
                    String.format("%s is not a file", file.getPath()),
                    FileTroubles.NOT_A_FILE,
                    file.toPath()
            );
        }
        //Checking existing of file
        if (!file.exists()) {
            throw new InvalidFileException(
                    String.format("%s doesn't exist", file.getPath()),
                    FileTroubles.FILE_NOT_EXISTS,
                    file.toPath()
            );
        }
        if (!file.canWrite()) {
            if (!file.setWritable(true)) {
                throw new InvalidFileException(
                        String.format("File %s cannot be set writable", file.getPath()),
                        FileTroubles.UNWRITABLE,
                        file.toPath()
                );
            }
        }
        //Construction of new file
        return new VirtualFileClass(
                VirtualFileDao.construct(
                     Long.parseLong(
                             //Name parsing
                             VirtualFileDao.getFileNamePattern().matcher(file.getName()).group(),
                             16
                     ),
                     file
                ),
                dependencies
        );
    }

    /**
     * This method checks existing of file and then create or open it
     * @param directory is directory that should contain File
     * @param id is ID of file and its name
     * @param dependencies is {@link VirtualFileDependencies} that will be used in new {@link VirtualFile} object.
     * @return {@link VirtualFile} object
     * @throws InvalidFileException for many reasons that would not allow to open file
     */
    public static VirtualFile createOrOpenFile(File directory, Long id, VirtualFileDependencies dependencies) {
        var file = new File(directory, Long.toHexString(id));
        if (file.exists()) {
            return openExistingFile(file, dependencies);
        }
        return createNewFile(directory, id, dependencies);
    }

    /**
     * Method returns specific Virtual File for testing needs
     * @param dao VF data
     * @param dependencies VF dependencies
     * @return Virtual File
     * @apiNote <b>only for tests</b>
     */
    public static VirtualFile constructSpecialVF(VirtualFileDao dao, VirtualFileDependencies dependencies) {
        return new VirtualFileClass(dao, dependencies);
    }

    public static VirtualFile constructTest(File dir, Long id) {
        File file;
        file = new File(dir, id.toString());
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        file.deleteOnExit();
        var dao = new VirtualFileDao(file, id);
        var dependencies = VirtualFileDependencies.getTest();
        return constructSpecialVF(dao, dependencies);
    }
}
